/*
 * Shader.cpp
 *
 *  Created on: May 28, 2011
 *      Author: Ernest Mashele <mashern@gmail.com>
 */

#include "Shader.h"
#include "Geometry.h"
#include "Material.h"

Shader::Shader() :
	pEffect(0), pEffectTechnique(0), pVertexLayout(0), WorldVariable(0),
			ViewVariable(0), ProjectionVariable(0), pTextureRV(0),
			pCameraPositionVariable(0), pAmbientVariable(0), pSpecularVariable(
					0), pDiffuseVariable(0), pSpecularPowerVariable(0)
{
}

Shader::~Shader()
{
	pCameraPositionVariable = 0;
	pAmbientVariable = 0;
	pDiffuseVariable = 0;
	pSpecularVariable = 0;
	pSpecularPowerVariable = 0;
	WorldVariable = 0;
	ViewVariable = 0;
	ProjectionVariable = 0;
	pTextureRV = 0;

	SafeRelease(pVertexLayout);
	SafeRelease(pEffect);
	pEffectTechnique = 0;
}

bool Shader::Initialize(ID3D10Device* pDevice, HWND hWnd)
{
	ID3D10Blob *pErrors = 0;
	HRESULT hr = D3DX10CreateEffectFromFile(L"shader.fx", NULL, NULL, "fx_4_0",
			D3D10_SHADER_ENABLE_STRICTNESS, 0, pDevice, 0, 0, &pEffect,
			&pErrors, &hr);

	if (FAILED(hr))
	{
		if (pErrors)
		{
			LPVOID compileErrors = pErrors->GetBufferPointer();
			using namespace std;
			string strErr = (const char*) compileErrors;
			wstring wcstrErr(strErr.begin(), strErr.end());
			return Error((WCHAR*) wcstrErr.c_str());
		}
		else
		{
			return Error(L"Shader file missing!");
		}
	}

	// Obtain the technique
	pEffectTechnique = pEffect->GetTechniqueByName("Default");
	if (!pEffectTechnique)
		return Error(L"Could not get the Technique");

	ObtainShaderVariables();

	// Create input layout
	if (!CreateInputLayout(pDevice))
		return false;

	return true;
}

void Shader::Render(ID3D10Device* device, int indexCount,
		D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
		D3DXMATRIX projectionMatrix, ID3D10ShaderResourceView* texture,
		D3DXVECTOR3 cameraPosition, D3DXVECTOR3 lightDirection,
		D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor,
		D3DXVECTOR4 specularColor, float specularPower)
{
	// Set the shader parameters that it will use for rendering.
	SetShaderParameters(worldMatrix, viewMatrix, projectionMatrix, texture,
			cameraPosition, lightDirection, ambientColor, diffuseColor,
			specularColor, specularPower);

	// Now render the prepared buffers with the shader.
	RenderShader(device, indexCount);

	return;
}

bool Shader::CreateInputLayout(ID3D10Device* device)
{

	using namespace ptn;

	D3D10_PASS_DESC passDesc;
	pEffectTechnique->GetPassByIndex(0)->GetDesc(&passDesc);

	// Create the input layout.
	HRESULT hr = device->CreateInputLayout(layout, numElements,
			passDesc.pIAInputSignature, passDesc.IAInputSignatureSize,
			&pVertexLayout);
	if (FAILED(hr))
	{
		return Error(L"Could not create input layout.");
	}

	return true;
}

void Shader::SetShaderParameters(D3DXMATRIX worldMatrix, D3DXMATRIX viewMatrix,
		D3DXMATRIX projectionMatrix, ID3D10ShaderResourceView* texture,
		D3DXVECTOR3 cameraPosition, D3DXVECTOR3 lightDirection,
		D3DXVECTOR4 ambientColor, D3DXVECTOR4 diffuseColor,
		D3DXVECTOR4 specularColor, float specularPower)
{
	// Set the world matrix variable inside the shader.
	WorldVariable->SetMatrix((float*) &worldMatrix);

	// Set the view matrix variable inside the shader.
	ViewVariable->SetMatrix((float*) &viewMatrix);

	// Set the projection matrix variable inside the shader.
	ProjectionVariable->SetMatrix((float*) &projectionMatrix);

	// Bind the texture.
	pTextureRV->SetResource(texture);

	// Set the position of the camera.
	pCameraPositionVariable->SetFloatVector((float*) &cameraPosition);

	// Set the ambient color of the light.
	pAmbientVariable->SetFloatVector((float*) &ambientColor);

	// Set the diffuse color of the light.
	pDiffuseVariable->SetFloatVector((float*) &diffuseColor);

	// Set the specular color of the light.
	pSpecularVariable->SetFloatVector((float*) &specularColor);

	// Set the specular power of the light.
	pSpecularPowerVariable->SetFloat(specularPower);

	return;
}

void Shader::RenderShader(ID3D10Device* device, int indexCount)
{
	D3D10_TECHNIQUE_DESC techniqueDesc;
	unsigned int i;
	device->IASetInputLayout(pVertexLayout);
	pEffectTechnique->GetDesc(&techniqueDesc);
	for (i = 0; i < techniqueDesc.Passes; ++i)
	{
		pEffectTechnique->GetPassByIndex(i)->Apply(0);
		device->DrawIndexed(indexCount, 0, 0);
	}

	return;
}

void Shader::ObtainShaderVariables()
{
	WorldVariable = pEffect->GetVariableByName("World")->AsMatrix();
	ViewVariable = pEffect->GetVariableByName("View")->AsMatrix();
	ProjectionVariable = pEffect->GetVariableByName("Projection")->AsMatrix();
	pAmbientVariable = pEffect->GetVariableByName("Ka")->AsVector();
	pDiffuseVariable = pEffect->GetVariableByName("Kd")->AsVector();
	pSpecularVariable = pEffect->GetVariableByName("Ks")->AsVector();
	pTextureRV = pEffect->GetVariableByName("DiffuseMap")->AsShaderResource();
	pCameraPositionVariable
			= pEffect->GetVariableByName("CameraPosition")->AsVector();
	pSpecularPowerVariable
			= pEffect->GetVariableByName("SpecularPower")->AsScalar();
}

void Shader::SetShaderVariables(D3DXMATRIX World, D3DXMATRIX View,
		D3DXMATRIX Projection, D3DXVECTOR3 CameraPosition, Material *p, ID3D10ShaderResourceView* pTexture)
{
	WorldVariable->SetMatrix(World);
	ViewVariable->SetMatrix(View);
	ProjectionVariable->SetMatrix(Projection);
	pCameraPositionVariable->SetFloatVector(CameraPosition);
	pAmbientVariable->SetFloatVector(p->getAmbientColour());
	pSpecularVariable->SetFloatVector(p->getSpecularColour());
	pDiffuseVariable->SetFloatVector(p->getDiffuseColour());
	pSpecularPowerVariable->SetFloat(p->getSpecularPower());
    pTextureRV->SetResource(pTexture);
}
